package com.aizuda.easyManagerTool.service.dbc.db.impl.spi.util;

import cn.hutool.core.io.unit.DataSizeUtil;
import cn.hutool.core.util.ObjectUtil;
import com.aizuda.easyManagerTool.domain.dto.dbc.db.DBEditDTO;
import com.aizuda.easyManagerTool.domain.dto.dbc.db.ForwardInfo;
import com.aizuda.easyManagerTool.domain.dto.terminal.SSHInfoDTO;
import com.aizuda.easyManagerTool.service.dbc.db.impl.spi.config.DriverConfig;
import com.aizuda.easyManagerTool.service.dbc.db.impl.spi.enums.DataTypeEnum;
import com.aizuda.easyManagerTool.service.dbc.db.impl.spi.sql.IDriverManager;
import com.aizuda.easyManagerTool.service.terminal.impl.TerminalSessionManager;
import com.aizuda.easyManagerTool.util.SSHManagerUtil;
import com.baomidou.mybatisplus.annotation.DbType;
import com.jcraft.jsch.Session;
import lombok.extern.slf4j.Slf4j;

import java.math.BigDecimal;
import java.sql.*;
import java.util.Map;

/**
 * jdbc工具类
 *
 * @author Jiaju Zhuang
 */
@Slf4j
public class JdbcUtils {

    private static final long MAX_RESULT_SIZE = 256 * 1024;

    /**
     * 获取德鲁伊的的数据库类型
     *
     * @param dbType
     * @return
     */
    public static DbType parse2DruidDbType(String dbType) {
        if (dbType == null) {
            return null;
        }
        try {
            return DbType.valueOf(dbType.toLowerCase());
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * 解析字段的类型
     *
     * @param typeName
     * @param type
     * @return
     */
    public static DataTypeEnum resolveDataType(String typeName, int type) {
        switch (getTypeByTypeName(typeName, type)) {
            case Types.BOOLEAN:
                return DataTypeEnum.BOOLEAN;
            case Types.CHAR:
            case Types.VARCHAR:
            case Types.NVARCHAR:
            case Types.LONGVARCHAR:
            case Types.LONGNVARCHAR:
                return DataTypeEnum.STRING;
            case Types.BIGINT:
            case Types.DECIMAL:
            case Types.DOUBLE:
            case Types.FLOAT:
            case Types.INTEGER:
            case Types.NUMERIC:
            case Types.REAL:
            case Types.SMALLINT:
                return DataTypeEnum.NUMERIC;
            case Types.BIT:
            case Types.TINYINT:
                if (typeName.toLowerCase().contains("bool")) {
                    // Declared as numeric but actually it's a boolean
                    return DataTypeEnum.BOOLEAN;
                }
                return DataTypeEnum.NUMERIC;
            case Types.DATE:
            case Types.TIME:
            case Types.TIME_WITH_TIMEZONE:
            case Types.TIMESTAMP:
            case Types.TIMESTAMP_WITH_TIMEZONE:
                return DataTypeEnum.DATETIME;
            case Types.BINARY:
            case Types.VARBINARY:
            case Types.LONGVARBINARY:
                return DataTypeEnum.BINARY;
            case Types.BLOB:
            case Types.CLOB:
            case Types.NCLOB:
            case Types.SQLXML:
                return DataTypeEnum.CONTENT;
            case Types.STRUCT:
                return DataTypeEnum.STRUCT;
            case Types.ARRAY:
                return DataTypeEnum.ARRAY;
            case Types.ROWID:
                return DataTypeEnum.ROWID;
            case Types.REF:
                return DataTypeEnum.REFERENCE;
            case Types.OTHER:
                return DataTypeEnum.OBJECT;
            default:
                return DataTypeEnum.UNKNOWN;
        }
    }

    private static int getTypeByTypeName(String typeName, int type) {
        // [JDBC: SQLite driver uses VARCHAR value type for all LOBs]
        if (type == Types.OTHER || type == Types.VARCHAR) {
            if ("BLOB".equalsIgnoreCase(typeName)) {
                return Types.BLOB;
            } else if ("CLOB".equalsIgnoreCase(typeName)) {
                return Types.CLOB;
            } else if ("NCLOB".equalsIgnoreCase(typeName)) {
                return Types.NCLOB;
            }
        } else if (type == Types.BIT) {
            // Workaround for MySQL (and maybe others) when TINYINT(1) == BOOLEAN
            if ("TINYINT".equalsIgnoreCase(typeName)) {
                return Types.TINYINT;
            }
        }
        return type;
    }

    /**
     * 获取一个返回值
     *
     * @param rs
     * @param index
     * @return
     * @throws SQLException
     */
    public static String getResultSetValue(ResultSet rs, int index, boolean limitSize) throws SQLException {
        Object obj = rs.getObject(index);
        if (obj == null) {
            return null;
        }
        try {
            if (obj instanceof BigDecimal) {
                BigDecimal bigDecimal = (BigDecimal)obj;
                return bigDecimal.toPlainString();
            } else if (obj instanceof Double) {
                Double d = (Double)obj;
                return BigDecimal.valueOf(d).toPlainString();
            } else if (obj instanceof Float) {
                Float f = (Float)obj;
                return BigDecimal.valueOf(f).toPlainString();
            } else if (obj instanceof Clob) {
                return largeString(rs, index, limitSize);
            } else if (obj instanceof byte[]) {
                return largeString(rs, index, limitSize);
            } else if (obj instanceof Blob) {
                Blob blob = (Blob)obj;
                return largeStringBlob(blob, limitSize);
            }
            return rs.getString(index);
        } catch (Exception e) {
            log.warn("解析数失败:{},{}", index, obj, e);
            return obj.toString();
        }
    }

    private static String largeStringBlob(Blob blob, boolean limitSize) throws SQLException {
        if (blob == null) {
            return null;
        }
        int length = Math.toIntExact(blob.length());
        if (limitSize && length > MAX_RESULT_SIZE) {
            length = Math.toIntExact(MAX_RESULT_SIZE);
        }
        byte[] data = blob.getBytes(1, length);
        String result = new String(data);

        if (length > MAX_RESULT_SIZE) {
            return "[ " + DataSizeUtil.format(MAX_RESULT_SIZE) + " of " + DataSizeUtil.format(length)
                + " ,"
                +  " ] " + result;
        }
        return result;
    }

    private static String largeString(ResultSet rs, int index, boolean limitSize) throws SQLException {
        String result = rs.getString(index);
        if (result == null) {
            return null;

        }
        if (!limitSize) {
            return result;
        }

        if (result.length() > MAX_RESULT_SIZE) {
            return "[ " + DataSizeUtil.format(MAX_RESULT_SIZE) + " of " + DataSizeUtil.format(result.length()) + " ,"
                + " ] " + result.substring(0,
                Math.toIntExact(MAX_RESULT_SIZE));
        }
        return result;
    }

    /**
     * 测试数据库连接
     * @return
     */
    public static void testConnect(DBEditDTO dto, SSHInfoDTO ssh, Map<String, Object> properties) throws Exception {
        Session session = null;
        Connection connection = null;
        DriverConfig driverConfig = new DriverConfig();
        driverConfig.setDbType(dto.getTypeName());
        driverConfig.setJdbcDriver(dto.getDriver().getDriverName());
        driverConfig.setJdbcDriverClass(dto.getDriver().getDriverClassName());
        // 加载驱动
        String url = dto.getDbUrl();
        try {
            if (dto.getDbServerEnable()) {
                ForwardInfo forwardInfo = ssh.getForwardInfo();
                forwardInfo.setRPort(dto.getDbPort());
                forwardInfo.setRHost(dto.getDbHosts());
                session = SSHManagerUtil.forwardSession(new TerminalSessionManager.Manager(),ssh);
                url = url.replace(dto.getDbHosts(), "127.0.0.1")
                        .replace(dto.getDbPort(), String.valueOf(forwardInfo.getLocalPort()));
            }
            // 创建连接
            connection = IDriverManager.getConnection(url, dto.getDbAccount(), dto.getDbPassword(), driverConfig, properties);
        } catch (Exception e) {
            throw e;
        } finally {
            if (connection != null) {
                try {
                    connection.close();
                } catch (SQLException e) {
                    // ignore
                }
            }
            if (session != null) {
                try {
                    if (ObjectUtil.isNotEmpty(ssh.getServerPort())) {
                        session.delPortForwardingL(ssh.getServerPort());
                    }
                    session.disconnect();
                } catch (Exception e) {

                }
            }
        }
    }

}
